/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package io.netty.util.internal;

import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import org.jctools.queues.MpscArrayQueue;
import org.jctools.queues.MpscChunkedArrayQueue;
import org.jctools.queues.MpscUnboundedArrayQueue;
import org.jctools.queues.SpscLinkedQueue;
import org.jctools.queues.atomic.MpscAtomicArrayQueue;
import org.jctools.queues.atomic.MpscGrowableAtomicArrayQueue;
import org.jctools.queues.atomic.MpscUnboundedAtomicArrayQueue;
import org.jctools.queues.atomic.SpscLinkedAtomicQueue;
import org.jctools.util.Pow2;
import org.jctools.util.UnsafeAccess;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Deque;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static io.netty.util.internal.PlatformDependent0.HASH_CODE_ASCII_SEED;
import static io.netty.util.internal.PlatformDependent0.HASH_CODE_C1;
import static io.netty.util.internal.PlatformDependent0.HASH_CODE_C2;
import static io.netty.util.internal.PlatformDependent0.hashCodeAsciiSanitize;
import static io.netty.util.internal.PlatformDependent0.unalignedAccess;
import static java.lang.Math.max;
import static java.lang.Math.min;

/**
 * Utility that detects various properties specific to the current runtime environment, such as Java
 * version and the availability of the {@code sun.misc.Unsafe} object.
 * <p>
 * You can disable the use of {@code sun.misc.Unsafe} if you specify the system property
 * <strong>io.netty.noUnsafe</strong>.
 */
public final class PlatformDependent {

  private static final InternalLogger logger = InternalLoggerFactory
      .getInstance(PlatformDependent.class);

  private static final Pattern MAX_DIRECT_MEMORY_SIZE_ARG_PATTERN = Pattern.compile(
      "\\s*-XX:MaxDirectMemorySize\\s*=\\s*([0-9]+)\\s*([kKmMgG]?)\\s*$");

  private static final boolean IS_WINDOWS = isWindows0();
  private static final boolean IS_OSX = isOsx0();

  private static final boolean MAYBE_SUPER_USER;

  private static final boolean CAN_ENABLE_TCP_NODELAY_BY_DEFAULT = !isAndroid();

  private static final Throwable UNSAFE_UNAVAILABILITY_CAUSE = unsafeUnavailabilityCause0();
  private static final boolean DIRECT_BUFFER_PREFERRED;
  private static final long MAX_DIRECT_MEMORY = maxDirectMemory0();

  private static final int MPSC_CHUNK_SIZE = 1024;
  private static final int MIN_MAX_MPSC_CAPACITY = MPSC_CHUNK_SIZE * 2;
  private static final int MAX_ALLOWED_MPSC_CAPACITY = Pow2.MAX_POW2;

  private static final long BYTE_ARRAY_BASE_OFFSET = byteArrayBaseOffset0();

  private static final File TMPDIR = tmpdir0();

  private static final int BIT_MODE = bitMode0();
  private static final String NORMALIZED_ARCH = normalizeArch(
      SystemPropertyUtil.get("os.arch", ""));
  private static final String NORMALIZED_OS = normalizeOs(SystemPropertyUtil.get("os.name", ""));

  private static final int ADDRESS_SIZE = addressSize0();
  private static final boolean USE_DIRECT_BUFFER_NO_CLEANER;
  private static final AtomicLong DIRECT_MEMORY_COUNTER;
  private static final long DIRECT_MEMORY_LIMIT;
  private static final ThreadLocalRandomProvider RANDOM_PROVIDER;
  private static final Cleaner CLEANER;
  private static final int UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD;

  public static final boolean BIG_ENDIAN_NATIVE_ORDER =
      ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;

  private static final Cleaner NOOP = new Cleaner() {
    @Override
    public void freeDirectBuffer(ByteBuffer buffer) {
      // NOOP
    }
  };

  static {
    if (javaVersion() >= 7) {
      RANDOM_PROVIDER = new ThreadLocalRandomProvider() {
        @Override
        public Random current() {
          return java.util.concurrent.ThreadLocalRandom.current();
        }
      };
    } else {
      RANDOM_PROVIDER = new ThreadLocalRandomProvider() {
        @Override
        public Random current() {
          return ThreadLocalRandom.current();
        }
      };
    }

    // Here is how the system property is used:
    //
    // * <  0  - Don't use cleaner, and inherit max direct memory from java. In this case the
    //           "practical max direct memory" would be 2 * max memory as defined by the JDK.
    // * == 0  - Use cleaner, Netty will not enforce max memory, and instead will defer to JDK.
    // * >  0  - Don't use cleaner. This will limit Netty's total direct memory
    //           (note: that JDK's direct memory limit is independent of this).
    long maxDirectMemory = SystemPropertyUtil.getLong("io.netty.maxDirectMemory", -1);

    if (maxDirectMemory == 0 || !hasUnsafe() || !PlatformDependent0
        .hasDirectBufferNoCleanerConstructor()) {
      USE_DIRECT_BUFFER_NO_CLEANER = false;
      DIRECT_MEMORY_COUNTER = null;
    } else {
      USE_DIRECT_BUFFER_NO_CLEANER = true;
      if (maxDirectMemory < 0) {
        maxDirectMemory = MAX_DIRECT_MEMORY;
        if (maxDirectMemory <= 0) {
          DIRECT_MEMORY_COUNTER = null;
        } else {
          DIRECT_MEMORY_COUNTER = new AtomicLong();
        }
      } else {
        DIRECT_MEMORY_COUNTER = new AtomicLong();
      }
    }
    logger.debug("-Dio.netty.maxDirectMemory: {} bytes", maxDirectMemory);
    DIRECT_MEMORY_LIMIT = maxDirectMemory >= 1 ? maxDirectMemory : MAX_DIRECT_MEMORY;

    int tryAllocateUninitializedArray =
        SystemPropertyUtil.getInt("io.netty.uninitializedArrayAllocationThreshold", 1024);
    UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD =
        javaVersion() >= 9 && PlatformDependent0.hasAllocateArrayMethod() ?
            tryAllocateUninitializedArray : -1;
    logger.debug("-Dio.netty.uninitializedArrayAllocationThreshold: {}",
        UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD);

    MAYBE_SUPER_USER = maybeSuperUser0();

    if (!isAndroid()) {
      // only direct to method if we are not running on android.
      // See https://github.com/netty/netty/issues/2604
      if (javaVersion() >= 9) {
        CLEANER = CleanerJava9.isSupported() ? new CleanerJava9() : NOOP;
      } else {
        CLEANER = CleanerJava6.isSupported() ? new CleanerJava6() : NOOP;
      }
    } else {
      CLEANER = NOOP;
    }

    // We should always prefer direct buffers by default if we can use a Cleaner to release direct buffers.
    DIRECT_BUFFER_PREFERRED = CLEANER != NOOP
        && !SystemPropertyUtil.getBoolean("io.netty.noPreferDirect", false);
    if (logger.isDebugEnabled()) {
      logger.debug("-Dio.netty.noPreferDirect: {}", !DIRECT_BUFFER_PREFERRED);
    }

    /*
     * We do not want to log this message if unsafe is explicitly disabled. Do not remove the explicit no unsafe
     * guard.
     */
    if (CLEANER == NOOP && !PlatformDependent0.isExplicitNoUnsafe()) {
      logger.info(
          "Your platform does not provide complete low-level API for accessing direct buffers reliably. "
              +
              "Unless explicitly requested, heap buffer will always be preferred to avoid potential system "
              +
              "instability.");
    }
  }

  public static boolean hasDirectBufferNoCleanerConstructor() {
    return PlatformDependent0.hasDirectBufferNoCleanerConstructor();
  }

  /**
   * 这里UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD只有在Java版本在9或9以上，并且可以通过UNSAFE来
   * 申请数组内存，那么其值才会为1024，否则为-1. 无论如何，该方法将返回一个长度为size的字节数组
   */
  public static byte[] allocateUninitializedArray(int size) {
    return UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD < 0
        || UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD > size ?
        new byte[size] : PlatformDependent0.allocateUninitializedArray(size);
  }

  /**
   * Returns {@code true} if and only if the current platform is Android
   */
  public static boolean isAndroid() {
    return PlatformDependent0.isAndroid();
  }

  /**
   * Return {@code true} if the JVM is running on Windows
   */
  public static boolean isWindows() {
    return IS_WINDOWS;
  }

  /**
   * Return {@code true} if the JVM is running on OSX / MacOS
   */
  public static boolean isOsx() {
    return IS_OSX;
  }

  /**
   * Return {@code true} if the current user may be a super-user. Be aware that this is just an hint
   * and so it may return false-positives.
   */
  public static boolean maybeSuperUser() {
    return MAYBE_SUPER_USER;
  }

  /**
   * Return the version of Java under which this library is used.
   */
  public static int javaVersion() {
    return PlatformDependent0.javaVersion();
  }

  /**
   * Returns {@code true} if and only if it is fine to enable TCP_NODELAY socket option by default.
   */
  public static boolean canEnableTcpNoDelayByDefault() {
    return CAN_ENABLE_TCP_NODELAY_BY_DEFAULT;
  }

  /**
   * Return {@code true} if {@code sun.misc.Unsafe} was found on the classpath and can be used for
   * accelerated direct memory access.
   */
  public static boolean hasUnsafe() {
    return UNSAFE_UNAVAILABILITY_CAUSE == null;
  }

  /**
   * Return the reason (if any) why {@code sun.misc.Unsafe} was not available.
   */
  public static Throwable getUnsafeUnavailabilityCause() {
    return UNSAFE_UNAVAILABILITY_CAUSE;
  }

  /**
   * {@code true} if and only if the platform supports unaligned access.
   *
   * @see <a href="http://en.wikipedia.org/wiki/Segmentation_fault#Bus_error">Wikipedia on
   * segfault</a>
   */
  public static boolean isUnaligned() {
    return PlatformDependent0.isUnaligned();
  }

  /**
   * Returns {@code true} if the platform has reliable low-level direct buffer access API and a user
   * has not specified {@code -Dio.netty.noPreferDirect} option.
   */
  public static boolean directBufferPreferred() {
    return DIRECT_BUFFER_PREFERRED;
  }

  /**
   * Returns the maximum memory reserved for direct buffer allocation.
   */
  public static long maxDirectMemory() {
    return DIRECT_MEMORY_LIMIT;
  }

  /**
   * Returns the temporary directory.
   */
  public static File tmpdir() {
    return TMPDIR;
  }

  /**
   * Returns the bit mode of the current VM (usually 32 or 64.)
   */
  public static int bitMode() {
    return BIT_MODE;
  }

  /**
   * Return the address size of the OS. 4 (for 32 bits systems ) and 8 (for 64 bits systems).
   */
  public static int addressSize() {
    return ADDRESS_SIZE;
  }

  public static long allocateMemory(long size) {
    return PlatformDependent0.allocateMemory(size);
  }

  public static void freeMemory(long address) {
    PlatformDependent0.freeMemory(address);
  }

  public static long reallocateMemory(long address, long newSize) {
    return PlatformDependent0.reallocateMemory(address, newSize);
  }

  /**
   * Raises an exception bypassing compiler checks for checked exceptions.
   */
  public static void throwException(Throwable t) {
    if (hasUnsafe()) {
      PlatformDependent0.throwException(t);
    } else {
      PlatformDependent.<RuntimeException>throwException0(t);
    }
  }

  @SuppressWarnings("unchecked")
  private static <E extends Throwable> void throwException0(Throwable t) throws E {
    throw (E) t;
  }

  /**
   * Creates a new fastest {@link ConcurrentMap} implementation for the current platform.
   */
  public static <K, V> ConcurrentMap<K, V> newConcurrentHashMap() {
    return new ConcurrentHashMap<K, V>();
  }

  /**
   * Creates a new fastest {@link LongCounter} implementation for the current platform.
   */
  public static LongCounter newLongCounter() {
    if (javaVersion() >= 8) {
      return new LongAdderCounter();
    } else {
      return new AtomicLongCounter();
    }
  }

  /**
   * Creates a new fastest {@link ConcurrentMap} implementation for the current platform.
   */
  public static <K, V> ConcurrentMap<K, V> newConcurrentHashMap(int initialCapacity) {
    return new ConcurrentHashMap<K, V>(initialCapacity);
  }

  /**
   * Creates a new fastest {@link ConcurrentMap} implementation for the current platform.
   */
  public static <K, V> ConcurrentMap<K, V> newConcurrentHashMap(int initialCapacity,
      float loadFactor) {
    return new ConcurrentHashMap<K, V>(initialCapacity, loadFactor);
  }

  /**
   * Creates a new fastest {@link ConcurrentMap} implementation for the current platform.
   */
  public static <K, V> ConcurrentMap<K, V> newConcurrentHashMap(
      int initialCapacity, float loadFactor, int concurrencyLevel) {
    return new ConcurrentHashMap<K, V>(initialCapacity, loadFactor, concurrencyLevel);
  }

  /**
   * Creates a new fastest {@link ConcurrentMap} implementation for the current platform.
   */
  public static <K, V> ConcurrentMap<K, V> newConcurrentHashMap(Map<? extends K, ? extends V> map) {
    return new ConcurrentHashMap<K, V>(map);
  }

  /**
   * Try to deallocate the specified direct {@link ByteBuffer}. Please note this method does nothing
   * if the current platform does not support this operation or the specified buffer is not a direct
   * buffer.
   */
  public static void freeDirectBuffer(ByteBuffer buffer) {
    CLEANER.freeDirectBuffer(buffer);
  }

  public static long directBufferAddress(ByteBuffer buffer) {
    return PlatformDependent0.directBufferAddress(buffer);
  }

  public static ByteBuffer directBuffer(long memoryAddress, int size) {
    if (PlatformDependent0.hasDirectBufferNoCleanerConstructor()) {
      return PlatformDependent0.newDirectBuffer(memoryAddress, size);
    }
    throw new UnsupportedOperationException(
        "sun.misc.Unsafe or java.nio.DirectByteBuffer.<init>(long, int) not available");
  }

  public static Object getObject(Object object, long fieldOffset) {
    return PlatformDependent0.getObject(object, fieldOffset);
  }

  public static int getInt(Object object, long fieldOffset) {
    return PlatformDependent0.getInt(object, fieldOffset);
  }

  public static byte getByte(long address) {
    return PlatformDependent0.getByte(address);
  }

  public static short getShort(long address) {
    return PlatformDependent0.getShort(address);
  }

  public static int getInt(long address) {
    return PlatformDependent0.getInt(address);
  }

  public static long getLong(long address) {
    return PlatformDependent0.getLong(address);
  }

  public static byte getByte(byte[] data, int index) {
    return PlatformDependent0.getByte(data, index);
  }

  public static short getShort(byte[] data, int index) {
    return PlatformDependent0.getShort(data, index);
  }

  public static int getInt(byte[] data, int index) {
    return PlatformDependent0.getInt(data, index);
  }

  public static long getLong(byte[] data, int index) {
    return PlatformDependent0.getLong(data, index);
  }

  private static long getLongSafe(byte[] bytes, int offset) {
    if (BIG_ENDIAN_NATIVE_ORDER) {
      return (long) bytes[offset] << 56 |
          ((long) bytes[offset + 1] & 0xff) << 48 |
          ((long) bytes[offset + 2] & 0xff) << 40 |
          ((long) bytes[offset + 3] & 0xff) << 32 |
          ((long) bytes[offset + 4] & 0xff) << 24 |
          ((long) bytes[offset + 5] & 0xff) << 16 |
          ((long) bytes[offset + 6] & 0xff) << 8 |
          (long) bytes[offset + 7] & 0xff;
    }
    return (long) bytes[offset] & 0xff |
        ((long) bytes[offset + 1] & 0xff) << 8 |
        ((long) bytes[offset + 2] & 0xff) << 16 |
        ((long) bytes[offset + 3] & 0xff) << 24 |
        ((long) bytes[offset + 4] & 0xff) << 32 |
        ((long) bytes[offset + 5] & 0xff) << 40 |
        ((long) bytes[offset + 6] & 0xff) << 48 |
        (long) bytes[offset + 7] << 56;
  }

  private static int getIntSafe(byte[] bytes, int offset) {
    if (BIG_ENDIAN_NATIVE_ORDER) {
      return bytes[offset] << 24 |
          (bytes[offset + 1] & 0xff) << 16 |
          (bytes[offset + 2] & 0xff) << 8 |
          bytes[offset + 3] & 0xff;
    }
    return bytes[offset] & 0xff |
        (bytes[offset + 1] & 0xff) << 8 |
        (bytes[offset + 2] & 0xff) << 16 |
        bytes[offset + 3] << 24;
  }

  private static short getShortSafe(byte[] bytes, int offset) {
    if (BIG_ENDIAN_NATIVE_ORDER) {
      return (short) (bytes[offset] << 8 | (bytes[offset + 1] & 0xff));
    }
    return (short) (bytes[offset] & 0xff | (bytes[offset + 1] << 8));
  }

  /**
   * Identical to {@link PlatformDependent0#hashCodeAsciiCompute(long, int)} but for {@link
   * CharSequence}.
   */
  private static int hashCodeAsciiCompute(CharSequence value, int offset, int hash) {
    if (BIG_ENDIAN_NATIVE_ORDER) {
      return hash * HASH_CODE_C1 +
          // Low order int
          hashCodeAsciiSanitizeInt(value, offset + 4) * HASH_CODE_C2 +
          // High order int
          hashCodeAsciiSanitizeInt(value, offset);
    }
    return hash * HASH_CODE_C1 +
        // Low order int
        hashCodeAsciiSanitizeInt(value, offset) * HASH_CODE_C2 +
        // High order int
        hashCodeAsciiSanitizeInt(value, offset + 4);
  }

  /**
   * Identical to {@link PlatformDependent0#hashCodeAsciiSanitize(int)} but for {@link
   * CharSequence}.
   */
  private static int hashCodeAsciiSanitizeInt(CharSequence value, int offset) {
    if (BIG_ENDIAN_NATIVE_ORDER) {
      // mimic a unsafe.getInt call on a big endian machine
      return (value.charAt(offset + 3) & 0x1f) |
          (value.charAt(offset + 2) & 0x1f) << 8 |
          (value.charAt(offset + 1) & 0x1f) << 16 |
          (value.charAt(offset) & 0x1f) << 24;
    }
    return (value.charAt(offset + 3) & 0x1f) << 24 |
        (value.charAt(offset + 2) & 0x1f) << 16 |
        (value.charAt(offset + 1) & 0x1f) << 8 |
        (value.charAt(offset) & 0x1f);
  }

  /**
   * Identical to {@link PlatformDependent0#hashCodeAsciiSanitize(short)} but for {@link
   * CharSequence}.
   */
  private static int hashCodeAsciiSanitizeShort(CharSequence value, int offset) {
    if (BIG_ENDIAN_NATIVE_ORDER) {
      // mimic a unsafe.getShort call on a big endian machine
      return (value.charAt(offset + 1) & 0x1f) |
          (value.charAt(offset) & 0x1f) << 8;
    }
    return (value.charAt(offset + 1) & 0x1f) << 8 |
        (value.charAt(offset) & 0x1f);
  }

  /**
   * Identical to {@link PlatformDependent0#hashCodeAsciiSanitize(byte)} but for {@link
   * CharSequence}.
   */
  private static int hashCodeAsciiSanitizeByte(char value) {
    return value & 0x1f;
  }

  public static void putByte(long address, byte value) {
    PlatformDependent0.putByte(address, value);
  }

  public static void putShort(long address, short value) {
    PlatformDependent0.putShort(address, value);
  }

  public static void putInt(long address, int value) {
    PlatformDependent0.putInt(address, value);
  }

  public static void putLong(long address, long value) {
    PlatformDependent0.putLong(address, value);
  }

  public static void putByte(byte[] data, int index, byte value) {
    PlatformDependent0.putByte(data, index, value);
  }

  public static void putShort(byte[] data, int index, short value) {
    PlatformDependent0.putShort(data, index, value);
  }

  public static void putInt(byte[] data, int index, int value) {
    PlatformDependent0.putInt(data, index, value);
  }

  public static void putLong(byte[] data, int index, long value) {
    PlatformDependent0.putLong(data, index, value);
  }

  public static void putObject(Object o, long offset, Object x) {
    PlatformDependent0.putObject(o, offset, x);
  }

  public static long objectFieldOffset(Field field) {
    return PlatformDependent0.objectFieldOffset(field);
  }

  public static void copyMemory(long srcAddr, long dstAddr, long length) {
    PlatformDependent0.copyMemory(srcAddr, dstAddr, length);
  }

  public static void copyMemory(byte[] src, int srcIndex, long dstAddr, long length) {
    PlatformDependent0.copyMemory(src, BYTE_ARRAY_BASE_OFFSET + srcIndex, null, dstAddr, length);
  }

  public static void copyMemory(long srcAddr, byte[] dst, int dstIndex, long length) {
    PlatformDependent0.copyMemory(null, srcAddr, dst, BYTE_ARRAY_BASE_OFFSET + dstIndex, length);
  }

  public static void setMemory(byte[] dst, int dstIndex, long bytes, byte value) {
    PlatformDependent0.setMemory(dst, BYTE_ARRAY_BASE_OFFSET + dstIndex, bytes, value);
  }

  public static void setMemory(long address, long bytes, byte value) {
    PlatformDependent0.setMemory(address, bytes, value);
  }

  /**
   * Allocate a new {@link ByteBuffer} with the given {@code capacity}. {@link ByteBuffer}s
   * allocated with this method <strong>MUST</strong> be deallocated via {@link
   * #freeDirectNoCleaner(ByteBuffer)}.
   */
  public static ByteBuffer allocateDirectNoCleaner(int capacity) {
    assert USE_DIRECT_BUFFER_NO_CLEANER;

    incrementMemoryCounter(capacity);
    try {
      return PlatformDependent0.allocateDirectNoCleaner(capacity);
    } catch (Throwable e) {
      decrementMemoryCounter(capacity);
      throwException(e);
      return null;
    }
  }

  /**
   * Reallocate a new {@link ByteBuffer} with the given {@code capacity}. {@link ByteBuffer}s
   * reallocated with this method <strong>MUST</strong> be deallocated via {@link
   * #freeDirectNoCleaner(ByteBuffer)}.
   */
  public static ByteBuffer reallocateDirectNoCleaner(ByteBuffer buffer, int capacity) {
    assert USE_DIRECT_BUFFER_NO_CLEANER;

    int len = capacity - buffer.capacity();
    incrementMemoryCounter(len);
    try {
      return PlatformDependent0.reallocateDirectNoCleaner(buffer, capacity);
    } catch (Throwable e) {
      decrementMemoryCounter(len);
      throwException(e);
      return null;
    }
  }

  /**
   * This method <strong>MUST</strong> only be called for {@link ByteBuffer}s that were allocated
   * via {@link #allocateDirectNoCleaner(int)}.
   */
  public static void freeDirectNoCleaner(ByteBuffer buffer) {
    assert USE_DIRECT_BUFFER_NO_CLEANER;

    int capacity = buffer.capacity();
    PlatformDependent0.freeMemory(PlatformDependent0.directBufferAddress(buffer));
    decrementMemoryCounter(capacity);
  }

  private static void incrementMemoryCounter(int capacity) {
    if (DIRECT_MEMORY_COUNTER != null) {
      long newUsedMemory = DIRECT_MEMORY_COUNTER.addAndGet(capacity);
      if (newUsedMemory > DIRECT_MEMORY_LIMIT) {
        DIRECT_MEMORY_COUNTER.addAndGet(-capacity);
        throw new OutOfDirectMemoryError("failed to allocate " + capacity
            + " byte(s) of direct memory (used: " + (newUsedMemory - capacity)
            + ", max: " + DIRECT_MEMORY_LIMIT + ')');
      }
    }
  }

  private static void decrementMemoryCounter(int capacity) {
    if (DIRECT_MEMORY_COUNTER != null) {
      long usedMemory = DIRECT_MEMORY_COUNTER.addAndGet(-capacity);
      assert usedMemory >= 0;
    }
  }

  public static boolean useDirectBufferNoCleaner() {
    return USE_DIRECT_BUFFER_NO_CLEANER;
  }

  /**
   * Compare two {@code byte} arrays for equality. For performance reasons no bounds checking on the
   * parameters is performed.
   *
   * @param bytes1 the first byte array.
   * @param startPos1 the position (inclusive) to start comparing in {@code bytes1}.
   * @param bytes2 the second byte array.
   * @param startPos2 the position (inclusive) to start comparing in {@code bytes2}.
   * @param length the amount of bytes to compare. This is assumed to be validated as not going out
   * of bounds by the caller.
   */
  public static boolean equals(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2,
      int length) {
    return !hasUnsafe() || !unalignedAccess() ?
        equalsSafe(bytes1, startPos1, bytes2, startPos2, length) :
        PlatformDependent0.equals(bytes1, startPos1, bytes2, startPos2, length);
  }

  /**
   * Determine if a subsection of an array is zero.
   *
   * @param bytes The byte array.
   * @param startPos The starting index (inclusive) in {@code bytes}.
   * @param length The amount of bytes to check for zero.
   * @return {@code false} if {@code bytes[startPos:startsPos+length)} contains a value other than
   * zero.
   */
  public static boolean isZero(byte[] bytes, int startPos, int length) {
    return !hasUnsafe() || !unalignedAccess() ?
        isZeroSafe(bytes, startPos, length) :
        PlatformDependent0.isZero(bytes, startPos, length);
  }

  /**
   * Compare two {@code byte} arrays for equality without leaking timing information. For
   * performance reasons no bounds checking on the parameters is performed.
   * <p>
   * The {@code int} return type is intentional and is designed to allow cascading of constant time
   * operations:
   * <pre>
   *     byte[] s1 = new {1, 2, 3};
   *     byte[] s2 = new {1, 2, 3};
   *     byte[] s3 = new {1, 2, 3};
   *     byte[] s4 = new {4, 5, 6};
   *     boolean equals = (equalsConstantTime(s1, 0, s2, 0, s1.length) &
   *                       equalsConstantTime(s3, 0, s4, 0, s3.length)) != 0;
   * </pre>
   *
   * @param bytes1 the first byte array.
   * @param startPos1 the position (inclusive) to start comparing in {@code bytes1}.
   * @param bytes2 the second byte array.
   * @param startPos2 the position (inclusive) to start comparing in {@code bytes2}.
   * @param length the amount of bytes to compare. This is assumed to be validated as not going out
   * of bounds by the caller.
   * @return {@code 0} if not equal. {@code 1} if equal.
   */
  public static int equalsConstantTime(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2,
      int length) {
    return !hasUnsafe() || !unalignedAccess() ?
        ConstantTimeUtils.equalsConstantTime(bytes1, startPos1, bytes2, startPos2, length) :
        PlatformDependent0.equalsConstantTime(bytes1, startPos1, bytes2, startPos2, length);
  }

  /**
   * Calculate a hash code of a byte array assuming ASCII character encoding. The resulting hash
   * code will be case insensitive.
   *
   * @param bytes The array which contains the data to hash.
   * @param startPos What index to start generating a hash code in {@code bytes}
   * @param length The amount of bytes that should be accounted for in the computation.
   * @return The hash code of {@code bytes} assuming ASCII character encoding. The resulting hash
   * code will be case insensitive.
   */
  public static int hashCodeAscii(byte[] bytes, int startPos, int length) {
    return !hasUnsafe() || !unalignedAccess() ?
        hashCodeAsciiSafe(bytes, startPos, length) :
        PlatformDependent0.hashCodeAscii(bytes, startPos, length);
  }

  /**
   * Calculate a hash code of a byte array assuming ASCII character encoding. The resulting hash
   * code will be case insensitive.
   * <p>
   * This method assumes that {@code bytes} is equivalent to a {@code byte[]} but just using {@link
   * CharSequence} for storage. The upper most byte of each {@code char} from {@code bytes} is
   * ignored.
   *
   * @param bytes The array which contains the data to hash (assumed to be equivalent to a {@code
   * byte[]}).
   * @return The hash code of {@code bytes} assuming ASCII character encoding. The resulting hash
   * code will be case insensitive.
   */
  public static int hashCodeAscii(CharSequence bytes) {
    int hash = HASH_CODE_ASCII_SEED;
    final int remainingBytes = bytes.length() & 7;
    // Benchmarking shows that by just naively looping for inputs 8~31 bytes long we incur a relatively large
    // performance penalty (only achieve about 60% performance of loop which iterates over each char). So because
    // of this we take special provisions to unroll the looping for these conditions.
    switch (bytes.length()) {
      case 31:
      case 30:
      case 29:
      case 28:
      case 27:
      case 26:
      case 25:
      case 24:
        hash = hashCodeAsciiCompute(bytes, bytes.length() - 24,
            hashCodeAsciiCompute(bytes, bytes.length() - 16,
                hashCodeAsciiCompute(bytes, bytes.length() - 8, hash)));
        break;
      case 23:
      case 22:
      case 21:
      case 20:
      case 19:
      case 18:
      case 17:
      case 16:
        hash = hashCodeAsciiCompute(bytes, bytes.length() - 16,
            hashCodeAsciiCompute(bytes, bytes.length() - 8, hash));
        break;
      case 15:
      case 14:
      case 13:
      case 12:
      case 11:
      case 10:
      case 9:
      case 8:
        hash = hashCodeAsciiCompute(bytes, bytes.length() - 8, hash);
        break;
      case 7:
      case 6:
      case 5:
      case 4:
      case 3:
      case 2:
      case 1:
      case 0:
        break;
      default:
        for (int i = bytes.length() - 8; i >= remainingBytes; i -= 8) {
          hash = hashCodeAsciiCompute(bytes, i, hash);
        }
        break;
    }
    switch (remainingBytes) {
      case 7:
        return ((hash * HASH_CODE_C1 + hashCodeAsciiSanitizeByte(bytes.charAt(0)))
            * HASH_CODE_C2 + hashCodeAsciiSanitizeShort(bytes, 1))
            * HASH_CODE_C1 + hashCodeAsciiSanitizeInt(bytes, 3);
      case 6:
        return (hash * HASH_CODE_C1 + hashCodeAsciiSanitizeShort(bytes, 0))
            * HASH_CODE_C2 + hashCodeAsciiSanitizeInt(bytes, 2);
      case 5:
        return (hash * HASH_CODE_C1 + hashCodeAsciiSanitizeByte(bytes.charAt(0)))
            * HASH_CODE_C2 + hashCodeAsciiSanitizeInt(bytes, 1);
      case 4:
        return hash * HASH_CODE_C1 + hashCodeAsciiSanitizeInt(bytes, 0);
      case 3:
        return (hash * HASH_CODE_C1 + hashCodeAsciiSanitizeByte(bytes.charAt(0)))
            * HASH_CODE_C2 + hashCodeAsciiSanitizeShort(bytes, 1);
      case 2:
        return hash * HASH_CODE_C1 + hashCodeAsciiSanitizeShort(bytes, 0);
      case 1:
        return hash * HASH_CODE_C1 + hashCodeAsciiSanitizeByte(bytes.charAt(0));
      default:
        return hash;
    }
  }

  private static final class Mpsc {

    private static final boolean USE_MPSC_CHUNKED_ARRAY_QUEUE;

    private Mpsc() {
    }

    static {
      Object unsafe = null;
      if (hasUnsafe()) {
        // jctools goes through its own process of initializing unsafe; of
        // course, this requires permissions which might not be granted to calling code, so we
        // must mark this block as privileged too
        unsafe = AccessController.doPrivileged(new PrivilegedAction<Object>() {
          @Override
          public Object run() {
            // force JCTools to initialize unsafe
            return UnsafeAccess.UNSAFE;
          }
        });
      }

      if (unsafe == null) {
        logger.debug("org.jctools-core.MpscChunkedArrayQueue: unavailable");
        USE_MPSC_CHUNKED_ARRAY_QUEUE = false;
      } else {
        logger.debug("org.jctools-core.MpscChunkedArrayQueue: available");
        USE_MPSC_CHUNKED_ARRAY_QUEUE = true;
      }
    }

    static <T> Queue<T> newMpscQueue(final int maxCapacity) {
      // Calculate the max capacity which can not be bigger then MAX_ALLOWED_MPSC_CAPACITY.
      // This is forced by the MpscChunkedArrayQueue implementation as will try to round it
      // up to the next power of two and so will overflow otherwise.
      final int capacity = max(min(maxCapacity, MAX_ALLOWED_MPSC_CAPACITY), MIN_MAX_MPSC_CAPACITY);
      return USE_MPSC_CHUNKED_ARRAY_QUEUE ? new MpscChunkedArrayQueue<T>(MPSC_CHUNK_SIZE, capacity)
          : new MpscGrowableAtomicArrayQueue<T>(MPSC_CHUNK_SIZE, capacity);
    }

    static <T> Queue<T> newMpscQueue() {
      return USE_MPSC_CHUNKED_ARRAY_QUEUE ? new MpscUnboundedArrayQueue<T>(MPSC_CHUNK_SIZE)
          : new MpscUnboundedAtomicArrayQueue<T>(MPSC_CHUNK_SIZE);
    }
  }

  /**
   * Create a new {@link Queue} which is safe to use for multiple producers (different threads) and
   * a single consumer (one thread!).
   *
   * @return A MPSC queue which may be unbounded.
   */
  public static <T> Queue<T> newMpscQueue() {
    return Mpsc.newMpscQueue();
  }

  /**
   * Create a new {@link Queue} which is safe to use for multiple producers (different threads) and
   * a single consumer (one thread!).
   */
  public static <T> Queue<T> newMpscQueue(final int maxCapacity) {
    return Mpsc.newMpscQueue(maxCapacity);
  }

  /**
   * Create a new {@link Queue} which is safe to use for single producer (one thread!) and a single
   * consumer (one thread!).
   */
  public static <T> Queue<T> newSpscQueue() {
    return hasUnsafe() ? new SpscLinkedQueue<T>() : new SpscLinkedAtomicQueue<T>();
  }

  /**
   * Create a new {@link Queue} which is safe to use for multiple producers (different threads) and
   * a single consumer (one thread!) with the given fixes {@code capacity}.
   */
  public static <T> Queue<T> newFixedMpscQueue(int capacity) {
    return hasUnsafe() ? new MpscArrayQueue<T>(capacity) : new MpscAtomicArrayQueue<T>(capacity);
  }

  /**
   * Return the {@link ClassLoader} for the given {@link Class}.
   */
  public static ClassLoader getClassLoader(final Class<?> clazz) {
    return PlatformDependent0.getClassLoader(clazz);
  }

  /**
   * Return the context {@link ClassLoader} for the current {@link Thread}.
   */
  public static ClassLoader getContextClassLoader() {
    return PlatformDependent0.getContextClassLoader();
  }

  /**
   * Return the system {@link ClassLoader}.
   */
  public static ClassLoader getSystemClassLoader() {
    return PlatformDependent0.getSystemClassLoader();
  }

  /**
   * Returns a new concurrent {@link Deque}.
   */
  public static <C> Deque<C> newConcurrentDeque() {
    if (javaVersion() < 7) {
      return new LinkedBlockingDeque<C>();
    } else {
      return new ConcurrentLinkedDeque<C>();
    }
  }

  /**
   * Return a {@link Random} which is not-threadsafe and so can only be used from the same thread.
   */
  public static Random threadLocalRandom() {
    return RANDOM_PROVIDER.current();
  }

  private static boolean isWindows0() {
    boolean windows = SystemPropertyUtil.get("os.name", "").toLowerCase(Locale.US).contains("win");
    if (windows) {
      logger.debug("Platform: Windows");
    }
    return windows;
  }

  private static boolean isOsx0() {
    String osname = SystemPropertyUtil.get("os.name", "").toLowerCase(Locale.US)
        .replaceAll("[^a-z0-9]+", "");
    boolean osx = osname.startsWith("macosx") || osname.startsWith("osx");

    if (osx) {
      logger.debug("Platform: MacOS");
    }
    return osx;
  }

  private static boolean maybeSuperUser0() {
    String username = SystemPropertyUtil.get("user.name");
    if (isWindows()) {
      return "Administrator".equals(username);
    }
    // Check for root and toor as some BSDs have a toor user that is basically the same as root.
    return "root".equals(username) || "toor".equals(username);
  }

  private static Throwable unsafeUnavailabilityCause0() {
    if (isAndroid()) {
      logger.debug("sun.misc.Unsafe: unavailable (Android)");
      return new UnsupportedOperationException("sun.misc.Unsafe: unavailable (Android)");
    }
    Throwable cause = PlatformDependent0.getUnsafeUnavailabilityCause();
    if (cause != null) {
      return cause;
    }

    try {
      boolean hasUnsafe = PlatformDependent0.hasUnsafe();
      logger.debug("sun.misc.Unsafe: {}", hasUnsafe ? "available" : "unavailable");
      return hasUnsafe ? null : PlatformDependent0.getUnsafeUnavailabilityCause();
    } catch (Throwable t) {
      logger.trace("Could not determine if Unsafe is available", t);
      // Probably failed to initialize PlatformDependent0.
      return new UnsupportedOperationException("Could not determine if Unsafe is available", t);
    }
  }

  private static long maxDirectMemory0() {
    long maxDirectMemory = 0;

    ClassLoader systemClassLoader = null;
    try {
      systemClassLoader = getSystemClassLoader();

      // When using IBM J9 / Eclipse OpenJ9 we should not use VM.maxDirectMemory() as it not reflects the
      // correct value.
      // See:
      //  - https://github.com/netty/netty/issues/7654
      String vmName = SystemPropertyUtil.get("java.vm.name", "").toLowerCase();
      if (!vmName.startsWith("ibm j9") &&
          // https://github.com/eclipse/openj9/blob/openj9-0.8.0/runtime/include/vendor_version.h#L53
          !vmName.startsWith("eclipse openj9")) {
        // Try to get from sun.misc.VM.maxDirectMemory() which should be most accurate.
        Class<?> vmClass = Class.forName("sun.misc.VM", true, systemClassLoader);
        Method m = vmClass.getDeclaredMethod("maxDirectMemory");
        maxDirectMemory = ((Number) m.invoke(null)).longValue();
      }
    } catch (Throwable ignored) {
      // Ignore
    }

    if (maxDirectMemory > 0) {
      return maxDirectMemory;
    }

    try {
      // Now try to get the JVM option (-XX:MaxDirectMemorySize) and parse it.
      // Note that we are using reflection because Android doesn't have these classes.
      Class<?> mgmtFactoryClass = Class.forName(
          "java.lang.management.ManagementFactory", true, systemClassLoader);
      Class<?> runtimeClass = Class.forName(
          "java.lang.management.RuntimeMXBean", true, systemClassLoader);

      Object runtime = mgmtFactoryClass.getDeclaredMethod("getRuntimeMXBean").invoke(null);

      @SuppressWarnings("unchecked")
      List<String> vmArgs = (List<String>) runtimeClass.getDeclaredMethod("getInputArguments")
          .invoke(runtime);
      for (int i = vmArgs.size() - 1; i >= 0; i--) {
        Matcher m = MAX_DIRECT_MEMORY_SIZE_ARG_PATTERN.matcher(vmArgs.get(i));
        if (!m.matches()) {
          continue;
        }

        maxDirectMemory = Long.parseLong(m.group(1));
        switch (m.group(2).charAt(0)) {
          case 'k':
          case 'K':
            maxDirectMemory *= 1024;
            break;
          case 'm':
          case 'M':
            maxDirectMemory *= 1024 * 1024;
            break;
          case 'g':
          case 'G':
            maxDirectMemory *= 1024 * 1024 * 1024;
            break;
        }
        break;
      }
    } catch (Throwable ignored) {
      // Ignore
    }

    if (maxDirectMemory <= 0) {
      maxDirectMemory = Runtime.getRuntime().maxMemory();
      logger.debug("maxDirectMemory: {} bytes (maybe)", maxDirectMemory);
    } else {
      logger.debug("maxDirectMemory: {} bytes", maxDirectMemory);
    }

    return maxDirectMemory;
  }

  private static File tmpdir0() {
    File f;
    try {
      f = toDirectory(SystemPropertyUtil.get("io.netty.tmpdir"));
      if (f != null) {
        logger.debug("-Dio.netty.tmpdir: {}", f);
        return f;
      }

      f = toDirectory(SystemPropertyUtil.get("java.io.tmpdir"));
      if (f != null) {
        logger.debug("-Dio.netty.tmpdir: {} (java.io.tmpdir)", f);
        return f;
      }

      // This shouldn't happen, but just in case ..
      if (isWindows()) {
        f = toDirectory(System.getenv("TEMP"));
        if (f != null) {
          logger.debug("-Dio.netty.tmpdir: {} (%TEMP%)", f);
          return f;
        }

        String userprofile = System.getenv("USERPROFILE");
        if (userprofile != null) {
          f = toDirectory(userprofile + "\\AppData\\Local\\Temp");
          if (f != null) {
            logger.debug("-Dio.netty.tmpdir: {} (%USERPROFILE%\\AppData\\Local\\Temp)", f);
            return f;
          }

          f = toDirectory(userprofile + "\\Local Settings\\Temp");
          if (f != null) {
            logger.debug("-Dio.netty.tmpdir: {} (%USERPROFILE%\\Local Settings\\Temp)", f);
            return f;
          }
        }
      } else {
        f = toDirectory(System.getenv("TMPDIR"));
        if (f != null) {
          logger.debug("-Dio.netty.tmpdir: {} ($TMPDIR)", f);
          return f;
        }
      }
    } catch (Throwable ignored) {
      // Environment variable inaccessible
    }

    // Last resort.
    if (isWindows()) {
      f = new File("C:\\Windows\\Temp");
    } else {
      f = new File("/tmp");
    }

    logger.warn("Failed to get the temporary directory; falling back to: {}", f);
    return f;
  }

  @SuppressWarnings("ResultOfMethodCallIgnored")
  private static File toDirectory(String path) {
    if (path == null) {
      return null;
    }

    File f = new File(path);
    f.mkdirs();

    if (!f.isDirectory()) {
      return null;
    }

    try {
      return f.getAbsoluteFile();
    } catch (Exception ignored) {
      return f;
    }
  }

  private static int bitMode0() {
    // Check user-specified bit mode first.
    int bitMode = SystemPropertyUtil.getInt("io.netty.bitMode", 0);
    if (bitMode > 0) {
      logger.debug("-Dio.netty.bitMode: {}", bitMode);
      return bitMode;
    }

    // And then the vendor specific ones which is probably most reliable.
    bitMode = SystemPropertyUtil.getInt("sun.arch.data.model", 0);
    if (bitMode > 0) {
      logger.debug("-Dio.netty.bitMode: {} (sun.arch.data.model)", bitMode);
      return bitMode;
    }
    bitMode = SystemPropertyUtil.getInt("com.ibm.vm.bitmode", 0);
    if (bitMode > 0) {
      logger.debug("-Dio.netty.bitMode: {} (com.ibm.vm.bitmode)", bitMode);
      return bitMode;
    }

    // os.arch also gives us a good hint.
    String arch = SystemPropertyUtil.get("os.arch", "").toLowerCase(Locale.US).trim();
    if ("amd64".equals(arch) || "x86_64".equals(arch)) {
      bitMode = 64;
    } else if ("i386".equals(arch) || "i486".equals(arch) || "i586".equals(arch) || "i686"
        .equals(arch)) {
      bitMode = 32;
    }

    if (bitMode > 0) {
      logger.debug("-Dio.netty.bitMode: {} (os.arch: {})", bitMode, arch);
    }

    // Last resort: guess from VM name and then fall back to most common 64-bit mode.
    String vm = SystemPropertyUtil.get("java.vm.name", "").toLowerCase(Locale.US);
    Pattern BIT_PATTERN = Pattern.compile("([1-9][0-9]+)-?bit");
    Matcher m = BIT_PATTERN.matcher(vm);
    if (m.find()) {
      return Integer.parseInt(m.group(1));
    } else {
      return 64;
    }
  }

  private static int addressSize0() {
    if (!hasUnsafe()) {
      return -1;
    }
    return PlatformDependent0.addressSize();
  }

  private static long byteArrayBaseOffset0() {
    if (!hasUnsafe()) {
      return -1;
    }
    return PlatformDependent0.byteArrayBaseOffset();
  }

  private static boolean equalsSafe(byte[] bytes1, int startPos1, byte[] bytes2, int startPos2,
      int length) {
    final int end = startPos1 + length;
    for (; startPos1 < end; ++startPos1, ++startPos2) {
      if (bytes1[startPos1] != bytes2[startPos2]) {
        return false;
      }
    }
    return true;
  }

  private static boolean isZeroSafe(byte[] bytes, int startPos, int length) {
    final int end = startPos + length;
    for (; startPos < end; ++startPos) {
      if (bytes[startPos] != 0) {
        return false;
      }
    }
    return true;
  }

  /**
   * Package private for testing purposes only!
   */
  static int hashCodeAsciiSafe(byte[] bytes, int startPos, int length) {
    int hash = HASH_CODE_ASCII_SEED;
    final int remainingBytes = length & 7;
    final int end = startPos + remainingBytes;
    for (int i = startPos - 8 + length; i >= end; i -= 8) {
      hash = PlatformDependent0.hashCodeAsciiCompute(getLongSafe(bytes, i), hash);
    }
    switch (remainingBytes) {
      case 7:
        return ((hash * HASH_CODE_C1 + hashCodeAsciiSanitize(bytes[startPos]))
            * HASH_CODE_C2 + hashCodeAsciiSanitize(getShortSafe(bytes, startPos + 1)))
            * HASH_CODE_C1 + hashCodeAsciiSanitize(getIntSafe(bytes, startPos + 3));
      case 6:
        return (hash * HASH_CODE_C1 + hashCodeAsciiSanitize(getShortSafe(bytes, startPos)))
            * HASH_CODE_C2 + hashCodeAsciiSanitize(getIntSafe(bytes, startPos + 2));
      case 5:
        return (hash * HASH_CODE_C1 + hashCodeAsciiSanitize(bytes[startPos]))
            * HASH_CODE_C2 + hashCodeAsciiSanitize(getIntSafe(bytes, startPos + 1));
      case 4:
        return hash * HASH_CODE_C1 + hashCodeAsciiSanitize(getIntSafe(bytes, startPos));
      case 3:
        return (hash * HASH_CODE_C1 + hashCodeAsciiSanitize(bytes[startPos]))
            * HASH_CODE_C2 + hashCodeAsciiSanitize(getShortSafe(bytes, startPos + 1));
      case 2:
        return hash * HASH_CODE_C1 + hashCodeAsciiSanitize(getShortSafe(bytes, startPos));
      case 1:
        return hash * HASH_CODE_C1 + hashCodeAsciiSanitize(bytes[startPos]);
      default:
        return hash;
    }
  }

  public static String normalizedArch() {
    return NORMALIZED_ARCH;
  }

  public static String normalizedOs() {
    return NORMALIZED_OS;
  }

  private static String normalize(String value) {
    return value.toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", "");
  }

  private static String normalizeArch(String value) {
    value = normalize(value);
    if (value.matches("^(x8664|amd64|ia32e|em64t|x64)$")) {
      return "x86_64";
    }
    if (value.matches("^(x8632|x86|i[3-6]86|ia32|x32)$")) {
      return "x86_32";
    }
    if (value.matches("^(ia64|itanium64)$")) {
      return "itanium_64";
    }
    if (value.matches("^(sparc|sparc32)$")) {
      return "sparc_32";
    }
    if (value.matches("^(sparcv9|sparc64)$")) {
      return "sparc_64";
    }
    if (value.matches("^(arm|arm32)$")) {
      return "arm_32";
    }
    if ("aarch64".equals(value)) {
      return "aarch_64";
    }
    if (value.matches("^(ppc|ppc32)$")) {
      return "ppc_32";
    }
    if ("ppc64".equals(value)) {
      return "ppc_64";
    }
    if ("ppc64le".equals(value)) {
      return "ppcle_64";
    }
    if ("s390".equals(value)) {
      return "s390_32";
    }
    if ("s390x".equals(value)) {
      return "s390_64";
    }

    return "unknown";
  }

  private static String normalizeOs(String value) {
    value = normalize(value);
    if (value.startsWith("aix")) {
      return "aix";
    }
    if (value.startsWith("hpux")) {
      return "hpux";
    }
    if (value.startsWith("os400")) {
      // Avoid the names such as os4000
      if (value.length() <= 5 || !Character.isDigit(value.charAt(5))) {
        return "os400";
      }
    }
    if (value.startsWith("linux")) {
      return "linux";
    }
    if (value.startsWith("macosx") || value.startsWith("osx")) {
      return "osx";
    }
    if (value.startsWith("freebsd")) {
      return "freebsd";
    }
    if (value.startsWith("openbsd")) {
      return "openbsd";
    }
    if (value.startsWith("netbsd")) {
      return "netbsd";
    }
    if (value.startsWith("solaris") || value.startsWith("sunos")) {
      return "sunos";
    }
    if (value.startsWith("windows")) {
      return "windows";
    }

    return "unknown";
  }

  private static final class AtomicLongCounter extends AtomicLong implements LongCounter {

    private static final long serialVersionUID = 4074772784610639305L;

    @Override
    public void add(long delta) {
      addAndGet(delta);
    }

    @Override
    public void increment() {
      incrementAndGet();
    }

    @Override
    public void decrement() {
      decrementAndGet();
    }

    @Override
    public long value() {
      return get();
    }
  }

  private interface ThreadLocalRandomProvider {

    Random current();
  }

  private PlatformDependent() {
    // only static method supported
  }
}
